Skip to content

docs(notes): measured .NET optimization limits and GC/LOH observability - #327

Merged
PhysShell merged 4 commits into
mainfrom
claude/dotnet-perf-research-6147se
Aug 9, 2026
Merged

docs(notes): measured .NET optimization limits and GC/LOH observability#327
PhysShell merged 4 commits into
mainfrom
claude/dotnet-perf-research-6147se

Conversation

@PhysShell

@PhysShell PhysShell commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Три исследовательские заметки с воспроизводимыми замерами, отвечающие на два вопроса: можно ли получить для .NET-кода оптимизации уровня C/C++ через LLVM, и что RyuJIT на самом деле оптимизирует. Ответы измерены, а не выведены из общих соображений; харнессы закоммичены и воспроизводят каждое число. Ничего не планируется в работу — это recorded, not scheduled.

Три результата решают вопрос про LLVM, и главный из них отрицательный для нас:

  • Наивное CIL-лоуринг (bounds-check с noreturn-броском на каждый ldelem, длина массива, перечитываемая из заголовка) полностью выключает автовекторизатор LLVM. Вынеси проверку — оба заблокированных кернела векторизуются. Включающая работа — это .NET-специфичный range-check elimination во фронтенде, а не то, что LLVM делает даром.
  • noalias дал 0% на всех замеренных кернелах: история «ownership → noalias → скорость», которая делает Rust быстрым, здесь не воспроизвелась. Это и есть вывод, что идея — чужой compiler-проект, а не проект Own.NET.
  • Написанный руками C# Vector256 сравнялся с LLVM -O3 на одном кернеле и обошёл clang -O3 в 1.8× на другом. LLVM здесь — автоматизация, а не недостижимый потолок.

Отдельная заметка измеряет разброс стоимости loop-invariant вызова: от 1.0× до 1092× при синтаксически одинаковой форме кода. Решающая пара — Any(x => x > threshold) при n=100 000: 8.3× против 1025× на посимвольно одинаковом исходнике, разница только в рантайм-значении и распределении данных. Отсюда вывод, что статическое правило и профайлер по отдельности неactionable и дополняют друг друга — то есть уже существующая в Plan.md схема Слой 1 → Слой 2.

Третья заметка берёт из разбора GCExperiment одну поправку с аргументом корректности: порог LOH сравнивается с полным размером объекта, поэтому byte[84_999] уезжает на LOH при 85 024 байтах, и фольклор «держи буферы под 85 000» промахивается на заголовок. Границу матрицы детектируемости при этом не двигает: LOH-фрагментация остаётся runtime-only.

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

Кода проекта изменение не трогает — только docs/notes/, поэтому тесты репозитория к нему не применимы и не гонялись.

Проверено то, что заметки утверждают:

  • docs/notes/llvm-codegen-feasibility-data/run.sh — прогнан от чистого состояния, воспроизводит матрицу векторизации (-Rpass=loop-vectorize), бенчмарк RyuJIT против LLVM и дамп дизассемблера RyuJIT.
  • docs/notes/invariant-cost-data/run.sh — прогнан, воспроизводит таблицу 1.0×–1092×.
  • Оба харнесса содержат встроенные гарантии от ошибок, которые были допущены при получении этих чисел: прогрев на 5000 вызовов (короткий прогрев оставлял tier-0 код в замере и завышал преимущество LLVM на ~40%) и correctness gate, требующий от всех вариантов K3 вернуть одно значение 25830282 (неравная ширина аккумулятора давала лестные 33×, которые были просто меньшей работой).
  • Относительные ссылки во всех трёх заметках проверены скриптом на существование целей.

Окружение замеров: Intel Xeon @ 2.10GHz (4 vCPU, AVX-512), clang/LLVM 18.1.3, .NET SDK 9.0.316, linux-x64.

Связанные issue

Нет. Ни одна заметка не заводит work item — по дисциплине research-landscape-2026.md заметки фиксируют, планирует ROADMAP.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет) — тестов нет, вместо них воспроизводимые харнессы с run.sh, прогнанные от чистого состояния
  • README/docs обновлены при необходимости — изменение целиком документационное; матрицы в ROADMAP.md/Plan.md намеренно не трогались, обе заметки объясняют почему
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Ограничения замеров записаны в самих заметках честно: одна машина с шумными соседями, четыре кернела — не корпус, C-сторона это прокси CIL-лоуринга, а не настоящий CIL, взаимодействие с GC не моделируется вообще, и .NET 4.7.2 (где был исходный случай) не замерялся — числа на .NET 9 являются нижней границей штрафа.

Отдельно: коммиты намеренно не сквошены — последовательность показывает, как первоначальная гипотеза про noalias была опровергнута измерением.


Generated by Claude Code

Summary by CodeRabbit

  • Documentation
    • Added technical notes on garbage collection, Large Object Heap behavior, loop-invariant performance, and LLVM code-generation feasibility.
    • Documented benchmark methodologies, measured results, limitations, and recommendations.
  • Benchmarks
    • Added reproducible .NET, C#, C, SIMD, and LLVM benchmark harnesses covering loop costs, array processing, filtering, reductions, and pointer traversal.
    • Added validation, warmup, timing, correctness checks, and scripts for running comparisons.

claude added 3 commits August 9, 2026 17:27
…s GC/LOH observability

Two research notes from a design discussion, both recorded and neither
scheduled, per the research-landscape-2026 discipline.

llvm-codegen-feasibility.md answers "can .NET code get C/C++-grade compiler
optimizations, and is a PoC doable?" with a committed, reproducible harness
rather than an opinion. Three results decide it:

- A naive CIL lowering (per-element bounds check with a noreturn throw, array
  length reloaded from the object header) switches LLVM's auto-vectorizer off
  entirely. Hoist the check and both blocked kernels vectorize. The enabling
  work is .NET-specific range-check elimination in the frontend, not anything
  LLVM does for free.
- noalias bought 0% on every kernel measured, so the ownership-to-noalias-to-
  speed story that makes Rust fast does not reproduce here. This is the finding
  that says the idea is a codegen project, not an Own.NET project.
- Hand-written C# Vector256 matched LLVM -O3 on one kernel and beat clang -O3
  by 1.8x on another, so LLVM is automation rather than a capability ceiling.

Also records what RyuJIT actually does (disassembly shows loop cloning, hoisted
length/null checks, a bounds-check-free hot loop, strength reduction) and what
it does not (vectorize, unroll) - and why no compiler hoists an opaque LINQ
call out of a loop, which makes that a static-analysis target rather than a
codegen one.

gc-observability-and-loh.md takes the one correction worth having from the
GCExperiment write-up: the LOH threshold is compared against full object size,
so byte[84_999] lands on the LOH at 85,024 bytes and the "keep buffers under
85,000" folklore is off by a header. The detectability-matrix boundary is
restated explicitly - cheaper GC observation is not an argument for static
inference, and LOH fragmentation stays runtime-only.

The harness enforces a 5000-call warmup and a cross-variant correctness gate
because both traps were hit while producing these numbers: a short warmup
inflated LLVM's advantage by ~40%, and an unequal accumulator width produced a
flattering 33x that was simply less work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbyzyVi7fibuSKLweuXgea
…yer load-bearing

Follow-up to llvm-codegen-feasibility.md, answering the objection that a static
rule can reach the shape of a loop-invariant call but never its cost.

Measured: four call shapes, each evaluated in-loop vs hoisted by hand, over
collection sizes 4..100_000. Every row is the same syntactic shape, so a static
rule matching that shape reports them identically. The penalty ranges from 1.0x
(List.Count, an O(1) property -- a textbook false positive) to 1092x
(OrderBy().First()).

The decisive row is Any(x => x > threshold) at n=100_000: 8.3x when the
predicate hits at element 0, 1025x when it never hits. The source is
character-for-character identical; only the runtime value of threshold and the
data distribution differ. No static analysis can separate those, and collection
size does not predict cost either -- the short-circuiting row is flat across n
while the other grows a hundredfold, so "flag only for large collections" is
wrong in both directions.

The conclusion is not that the static rule is worthless but that it and a
profiler are each unactionable alone and complete each other: the profiler
supplies magnitude without knowing the call is safely hoistable, the rule
supplies the proof and the fix without knowing whether it buys 0% or 99.9%.
That is Plan.md's existing Layer 1 -> Layer 2 shape, reusing the same
confirmation pattern already used for subscription leaks, with a timing witness
in place of a heap walk. It also repairs this note's parent, which flagged
"expensive" as a false-positive generator: that holds for a static predicate,
but measured it stops being a predicate and becomes a number.

Also expands the Burst row of the landscape table into a full subsection, since
the mechanism matters: Burst does not defeat the blockers found earlier, it
defines them out of the language (no object header means no per-iteration ldlen;
job-system bounds are loop-invariant by construction). Its value to us is cost
calibration first, a real but partial design precedent second -- the parallel to
OwnLang's buffer policies holds only for the restricted-language part, since the
ownership content was measured at 0% -- and nothing as a component.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbyzyVi7fibuSKLweuXgea
The invariant-cost note filed a LINQ loop-invariance rule under "P-036
territory". P-036 is the right host for the machinery (call graph,
MethodSummary, SCC composition), but none of its five summary domains --
ownership, obligation, progress, region, task -- is a purity/effect-freedom
domain, and effects are owned by P-008, which is explicitly horizon. A reader
would otherwise open P-036 expecting to find the domain there.

Also records that P-036's unknown/external-call policy would classify
Any(userLambda) as unresolved or unsupported, so under its own rules the static
half yields a candidate with declared uncertainty rather than a verdict --
independently reaching the note's conclusion that magnitude comes from the
runtime layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbyzyVi7fibuSKLweuXgea
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d298e553-0fe7-42ab-b6d2-6512515feeed

📥 Commits

Reviewing files that changed from the base of the PR and between c6578d2 and 0cd4311.

📒 Files selected for processing (7)
  • docs/notes/gc-observability-and-loh.md
  • docs/notes/invariant-cost-data/Program.cs
  • docs/notes/invariant-cost-data/run.sh
  • docs/notes/invariant-cost-static-vs-runtime.md
  • docs/notes/llvm-codegen-feasibility-data/Kernels.cs
  • docs/notes/llvm-codegen-feasibility-data/README.md
  • docs/notes/llvm-codegen-feasibility.md
📝 Walkthrough

Walkthrough

Added three research areas: GC and LOH observability, loop-invariant runtime costs, and LLVM code-generation feasibility. The changes include technical notes, .NET 9 benchmark programs, native C kernels, project files, and reproducible benchmark scripts.

Changes

GC observability and LOH

Layer / File(s) Summary
GC and LOH observability note
docs/notes/gc-observability-and-loh.md
Documents LOH threshold guidance, GC experiments, measurement caveats, runtime observation, static checks, and follow-up work.

Loop-invariant cost analysis

Layer / File(s) Summary
Invariant operation benchmark
docs/notes/invariant-cost-data/*
Adds a .NET 9 benchmark for repeated and hoisted Any, Count, OrderBy().First(), and List.Count operations. The runner builds Release output with tiered compilation disabled and reports timing ratios.
Invariant cost findings
docs/notes/invariant-cost-static-vs-runtime.md
Reports measured penalties, relates static analysis to runtime profiling, and records implementation limitations and scheduling status.

LLVM code-generation feasibility

Layer / File(s) Summary
Native kernel probes
docs/notes/llvm-codegen-feasibility-data/*.c
Adds C kernels that compare aliasing guarantees, range-check placement, array-length handling, reduction, filtering, AXPY, and pointer chasing.
Managed and interop benchmark harness
docs/notes/llvm-codegen-feasibility-data/Program.cs, docs/notes/llvm-codegen-feasibility-data/Kernels.cs
Adds managed scalar and SIMD implementations, native interop, warmups, timing, correctness checks, throughput reporting, and pointer-chasing measurements.
Build and measurement workflow
docs/notes/llvm-codegen-feasibility-data/bench.csproj, docs/notes/llvm-codegen-feasibility-data/run.sh, docs/notes/llvm-codegen-feasibility-data/README.md
Configures the .NET benchmark and automates native builds, LLVM vectorization checks, repeated measurements, and JIT assembly extraction.
LLVM feasibility findings
docs/notes/llvm-codegen-feasibility.md
Documents measured LLVM and RyuJIT behavior, kernel results, implementation options, recommendations, and limitations.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main documentation changes on .NET optimization limits and GC/LOH observability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/dotnet-perf-research-6147se

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6578d2fb7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/notes/llvm-codegen-feasibility-data/native2.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/notes/gc-observability-and-loh.md`:
- Around line 15-28: Update the LOH sizing guidance to account for effective
runtime configuration rather than treating 85,000 as universal: mention that
System.GC.LOHThreshold or DOTNET_GCLOHThreshold may override the default, and
avoid asserting a fixed 24-byte array header because object layout and alignment
vary by platform. Identify the measured environment, such as .NET 9 CoreCLR x64,
or make the recommendation simply to measure.
- Around line 56-63: Revise the GC probe description to identify
GC.CollectionCount(n) and GC.GetGCMemoryInfo() as process-wide heuristics
affected by prior, parallel, or background collections, not isolated scenario
snapshots. Before using the probe as an assertion, record the collection count
at the scenario boundary and compare GetGCMemoryInfo() only when the
corresponding GC index matches; retain the existing debug-only caveat.

In `@docs/notes/invariant-cost-data/Program.cs`:
- Around line 51-56: Update the comment above C_Inline to describe
OrderBy(...).First() as repeated linear scanning and key comparison via LINQ’s
specialized path, not allocation and full sorting on every iteration. Keep the
benchmark code unchanged unless the intended measurement is full sorting, in
which case enumerate the ordered sequence before taking the first element.
- Around line 94-99: Update Row to evaluate inline() and hoisted() before
calling Bench, and throw when their results differ instead of only appending a
mismatch marker; keep timing and output for equivalent implementations unchanged
so run.sh receives a non-zero exit status on failure.
- Around line 34-39: Rename the shape B benchmark label and its corresponding
Markdown row to explicitly describe Count() on a Select-wrapped sequence rather
than all IEnumerable<T> values. Update the visible labels around B_Inline and
the matching section near the later referenced lines, while leaving the
benchmark implementation unchanged.

In `@docs/notes/invariant-cost-data/run.sh`:
- Line 3: Update the run script’s .NET SDK requirement so it either enforces SDK
version 9.0.316 and reports the selected runtime with dotnet --info, or revise
the documentation to explicitly support any .NET 9 patch version. Ensure the
measurement command’s actual SDK/runtime selection is visible and consistent
with the reproducibility claim.
- Around line 5-7: Update the run script’s WORK initialization to track whether
the directory was created by the script, then register an EXIT trap that removes
only that owned temporary directory. Preserve caller-provided WORK directories
without deleting them.
- Around line 10-11: Update the benchmark mode labeling in
docs/notes/invariant-cost-data/run.sh lines 10-11 and
docs/notes/invariant-cost-static-vs-runtime.md lines 19-22 to consistently
describe DOTNET_TieredCompilation=0 as non-tiered mode, removing references to
tier-1 measurement unless the benchmark is intentionally changed to measure
tier-1 promotion.

In `@docs/notes/invariant-cost-static-vs-runtime.md`:
- Around line 42-45: Revise the conclusion in “The dynamic range is three orders
of magnitude” to limit claims to the measured sample: replace “costing exactly
nothing” with “no measurable penalty in this run,” and replace “useless for
prioritisation” with “cannot prioritize this sample without runtime data.”
- Around line 116-118: Update the statement in the .NET 4.7.2 measurement
discussion to remove the unsupported “lower bound” inference and describe the
.NET 9 results as not comparable to .NET Framework. Retain the fact that .NET
4.7.2 was not measured, and only make a lower-bound claim if matched Framework
measurements are added.
- Around line 54-56: Revise the claim in the discussion around Main and A_Inline
to scope it to analyzers that lack runtime or call-site input values.
Acknowledge that whole-program analysis can distinguish the benchmark’s
deterministic Enumerable.Range and constant arguments, while preserving the
conclusion that a local rule cannot predict costs for unknown general inputs.

In `@docs/notes/llvm-codegen-feasibility-data/Kernels.cs`:
- Around line 135-142: Update the K3 correctness gate around FilterSumManaged,
FilterSumBranchless, FilterSumSimd, FilterSumNative, and FilterSumFree to define
a named expected-result constant of 25830282 and validate r0 against it before
the existing pairwise implementation comparisons; retain the current mismatch
reporting for disagreements between variants.

In `@docs/notes/llvm-codegen-feasibility.md`:
- Around line 44-48: Update the K3 correctness-gate text to state that the
harness checks five variants, adding both native implementations—per-element
check and hoisted check—to the existing scalar, branchless, and hand-SIMD list.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b4d00fff-f239-4681-9af7-53da646990ee

📥 Commits

Reviewing files that changed from the base of the PR and between 5cf36c0 and c6578d2.

📒 Files selected for processing (16)
  • docs/notes/gc-observability-and-loh.md
  • docs/notes/invariant-cost-data/Program.cs
  • docs/notes/invariant-cost-data/linq.csproj
  • docs/notes/invariant-cost-data/run.sh
  • docs/notes/invariant-cost-static-vs-runtime.md
  • docs/notes/llvm-codegen-feasibility-data/Kernels.cs
  • docs/notes/llvm-codegen-feasibility-data/Program.cs
  • docs/notes/llvm-codegen-feasibility-data/README.md
  • docs/notes/llvm-codegen-feasibility-data/bench.csproj
  • docs/notes/llvm-codegen-feasibility-data/checkfree.c
  • docs/notes/llvm-codegen-feasibility-data/lenprobe.c
  • docs/notes/llvm-codegen-feasibility-data/matrix.c
  • docs/notes/llvm-codegen-feasibility-data/native.c
  • docs/notes/llvm-codegen-feasibility-data/native2.c
  • docs/notes/llvm-codegen-feasibility-data/run.sh
  • docs/notes/llvm-codegen-feasibility.md

Comment thread docs/notes/gc-observability-and-loh.md Outdated
Comment thread docs/notes/gc-observability-and-loh.md
Comment thread docs/notes/invariant-cost-data/Program.cs Outdated
Comment thread docs/notes/invariant-cost-data/Program.cs Outdated
Comment thread docs/notes/invariant-cost-data/Program.cs Outdated
Comment thread docs/notes/invariant-cost-static-vs-runtime.md Outdated
Comment thread docs/notes/invariant-cost-static-vs-runtime.md Outdated
Comment thread docs/notes/invariant-cost-static-vs-runtime.md Outdated
Comment thread docs/notes/llvm-codegen-feasibility-data/Kernels.cs Outdated
Comment thread docs/notes/llvm-codegen-feasibility.md Outdated
…e the gate fail

Verified each finding before acting; all are accuracy fixes to my own notes and
harnesses, and none changes a measured result.

Harness correctness. The invariant-cost Row() only PRINTED a mismatch, so a
non-equivalent pair would still exit 0 and be reported as a measurement -- it
now checks before timing and throws, which also makes the note's "asserted"
wording true. The K3 gate compared the five variants only to each other, where a
shared defect passes; it now also pins the expected constant 25830282. run.sh no
longer leaks the mktemp workdir it creates (trap on EXIT, only when it owns the
directory) and prints the SDK and runtime it actually selected instead of merely
claiming 9.0.316.

Overclaims scoped to the evidence. "No static analysis can separate those two,
ever" was too strong: Main builds data with a deterministic Enumerable.Range and
passes constant thresholds, so a whole-program analyzer could constant-propagate
these particular sites. The claim is now about a local rule and about general
unknown inputs, which is the case a real rule faces. "Costing exactly nothing"
and "useless for prioritisation" become no measurable penalty in this run and
cannot rank this sample without runtime data. The .NET 9 numbers are no longer
called a lower bound for 4.7.2 -- different JIT and different LINQ, so they are
simply not comparable without a matched Framework measurement.

LOH guidance corrected in two ways: 85,000 is the default, movable by
System.GC.LOHThreshold and DOTNET_GCLOHThreshold, and the 24-byte overhead is a
CoreCLR-x64 detail rather than a portable constant, so the portable advice is to
measure. The GC probe now carries the caveat that CollectionCount and
GetGCMemoryInfo are process-wide, moved by background and parallel GC, with
GetGCMemoryInfo zero-valued at Index 0 when no collection of that kind occurred.

Labels made accurate: five K3 variants rather than four, Count() on a
Select-wrapped sequence rather than all IEnumerable, OrderBy().First() as
repeated linear key scanning via .NET 9's TryGetFirst rather than a full sort
per iteration, and DOTNET_TieredCompilation=0 described as non-tiered FullOpts
rather than tier 1.

Both harnesses re-run clean: the vectorization matrix is unchanged and every
reported number reproduces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbyzyVi7fibuSKLweuXgea
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

@coderabbitai review

@PhysShell
PhysShell merged commit 7c10f37 into main Aug 9, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants